package me.chenqiang.cinc.transformers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import me.chenqiang.cinc.CincCodingException;
import me.chenqiang.cinc.CincCommons;
import me.chenqiang.cinc.IDType;
import me.chenqiang.cinc.codes.ProvinceUtils;

public class CitizenIDCodec {
	private static final Pattern PATTERN = Pattern.compile("^([1234568]\\d)(\\d{4})"
			+ "((?:19|20)\\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|1\\d|2\\d|3[01]))"
			+ "(\\d{3})([0-9X])$");
	
	private CitizenIDCodec() {
	}


	public static IDType validate(String number) {
		if(!PATTERN.matcher(number).matches()) {
			return null;
		}
		if(CincCommons.calculateCheckCode(number.substring(0, 17)) != number.charAt(17)) {
			return null;
		}
		return IDType.CITIZEN_ID;
	}

	
	public static String compress(String number)  throws CincCodingException{
		Matcher matcher = PATTERN.matcher(number);
		if(!matcher.matches()) {
			throw new CincCodingException(number);
		}
		
		long provinceIndex = ProvinceUtils.getProvinceIndex(matcher.group(1));
		long region = Long.parseLong(matcher.group(2));
		
		long encoded = (provinceIndex * 10000 + region) << 26 
				| (CincCommons.calculateDaysFrom1900(matcher.group(3)) & 0xffffL) << 10
				| Long.parseLong(matcher.group(4));
		
		return CincCommons.toSymbols(encoded, 9, '0');
	}

	public static String decompress(String symbols)  throws CincCodingException{
		long encoded = CincCommons.toNumbers(symbols);
		List<String> parts = new ArrayList<>();
		parts.add(String.format("%03d", encoded & 0x3ff));
		encoded = encoded >> 10;
		parts.add(CincCommons.calculateDateByDaysFrom1900(encoded & 0xffff));
		encoded = encoded >> 16;
		parts.add(String.format("%04d", encoded % 10000));
		parts.add(ProvinceUtils.getProvinceCode((int)encoded / 10000));		
		Collections.reverse(parts);
		
		StringBuilder sb = new StringBuilder();
		for(String p: parts) {
			sb.append(p);
		}
		
		char checkCode = CincCommons.calculateCheckCode(sb);
		sb.append(checkCode);
		
		return sb.toString();
	}

}
